/**
 * @file Define the the Web Application
 * 
 * @author Steve Gao<gaofu@parllay.com>
 */
const ExpressServer         = require('../servers/express-server').ExpressServer;

const Application           = require('./application').Application;
const UserService           = require('../services/user-service').UserService;
const WallService           = require('../services/wall-service').WallService;
const SalesService          = require('../services/sales-service').SalesService;
const WeChatService         = require('../services/wechat-service').WeChatService;
const WeiboService          = require('../services/weibo-service').WeiboService;
const WeChatMessageService  = require('../services/wechat-message-service').WeChatMessageService;
const QiniuService          = require('../services/qiniu-service').QiniuService;
const OpsService            = require('../services/ops-service').OpsService;
const MailService           = require('../services/mail-service').MailService;
const DianpingService       = require('../services/dianping-service').DianpingService;
const BlueMPService         = require('../services/bluemp-service').BlueMPService;
const I18NService           = require('../services/i18n-service').I18NService;
const CrawlerService        = require('../services/crawler-service').CrawlerService;
const PartnerService        = require('../services/partner-service').PartnerService;

const ApplicationController = require('../controllers/web/application-controller').ApplicationController;
const DefaultController     = require('../controllers/web/default-controller').DefaultController;
const DashboardController   = require('../controllers/web/dashboard-controller').DashboardController;
const WallController        = require('../controllers/web/wall-controller').WallController;
const ErrorController       = require('../controllers/web/error-controller').ErrorController;
const MerchantController    = require('../controllers/web/merchant-controller').MerchantController;
const MarketingController   = require('../controllers/web/marketing-controller').MarketingController;
const WeChatController      = require('../controllers/web/wechat-controller').WeChatController;
const WeiboController       = require('../controllers/web/weibo-controller').WeiboController;
const SettingsController    = require('../controllers/web/settings-controller').SettingsController;
const QiniuController       = require('../controllers/web/qiniu-controller').QiniuController;
const BlueMPController      = require('../controllers/web/bluemp-controller').BlueMPController;
const DianpingController    = require('../controllers/web/dianping-controller').DianpingController;
const PartnerController     = require('../controllers/web/partner-controller').PartnerController;

const {Configuration, SystemServices, ApplicationEvents} = require('../constants');

/**
 * The OnWall Web Application
 * 
 * @class WebApplication
 * @extends Application
 */
class WebApplication extends Application {

    get name() {
        return this.Configuration.name;
    }

    get opsService() {
        if (!this._opsService) {
            this._opsService = this.getSystemService(SystemServices.OPS_SERVICE);
        }

        return this._opsService;
    }

    constructor(args) {
        super(args);
        this.logger.info(`Initializing ${this.name} ...`)
        this._IsUnderMaintainancing = false;
        this.setMaxListeners(this.Configuration.max_event_listeners);
        this.setupApplicationContext();
        this.setupExpressServer();
        this.initializeEventHandler();
    }

    get ExpressServer() {
        if (!this._expressServer) {
            this._expressServer = new ExpressServer(this);
        }

        return this._expressServer;
    }

    /**
     * The application configuration
     * 
     * @name Configuration
     * @returns
     * 
     * @memberOf WebApplication
     */
    get Configuration() {
        if (!this._Configuration) {
            this._Configuration = this.Context.getConfiguration(Configuration.APPLICATION);
        }

        return this._Configuration;
    }

    get IsUnderMaintainancing() {
        return this._IsUnderMaintainancing;
    }

    /**
     * Wrap the getSystemService method of ApplicationContext
     * 
     * @name getSystemService
     * @param {String} name - The service name
     * 
     * @returns {Service}
     * @memberOf Application
     */
    getSystemService(name) {
        return this.Context.getSystemService(name);
    }

    /**
     * Wrap the getConfiguration method of ApplicationContext
     * 
     * @name getConfiguration
     * @param {String} configName
     * 
     * @returns {Object}
     * @memberOf Application
     */
    getConfiguration(configName) {
        return this.Context.getConfiguration(configName);
    }

    /**
     * Setup the application context
     * 
     * @name setupApplicationContext
     * 
     * @returns {void}
     */
    setupApplicationContext() {
        this.logger.info('Begin to register dependency services into application context ...');
        this.Context.registerSystemService(SystemServices.USER_SERVICE, UserService);
        this.Context.registerSystemService(SystemServices.SALES_SERVICE, SalesService);
        this.Context.registerSystemService(SystemServices.WALL_SERVICE, WallService);
        this.Context.registerSystemService(SystemServices.WECHAT_SERVICE, WeChatService);
        this.Context.registerSystemService(SystemServices.WEIBO_SERVICE, WeiboService);
        this.Context.registerSystemService(SystemServices.WECHAT_MESSAGE_SERVICE, WeChatMessageService);
        this.Context.registerSystemService(SystemServices.QINIU_SERVICE, QiniuService);
        this.Context.registerSystemService(SystemServices.OPS_SERVICE, OpsService);
        this.Context.registerSystemService(SystemServices.DIANPING_SERVICE, DianpingService);
        this.Context.registerSystemService(SystemServices.MAIL_SERVICE, MailService);
        this.Context.registerSystemService(SystemServices.BLUEMP_SERVICE, BlueMPService);
        this.Context.registerSystemService(SystemServices.I18N_SERVICE, I18NService);
        this.Context.registerSystemService(SystemServices.CRAWLER_SERVICE, CrawlerService);
        this.Context.registerSystemService(SystemServices.PARTNER_SERVICE, PartnerService);
        this.logger.info('Finished registering dependency services.');

        this.setupProcessName();
    }

    setupProcessName() {
        process.name = this.name;
    }

    /**
     * Setup the express server 
     * 
     * @name setupExpressServer
     * 
     * @returns {void}
     */
    setupExpressServer() {
        this.logger.info('Begin to register Web Controlers into web server ...');
        this.ExpressServer.registerWebController(ApplicationController);
        this.ExpressServer.registerWebController(WeChatController);
        this.ExpressServer.registerWebController(WeiboController);
        this.ExpressServer.registerWebController(QiniuController);
        this.ExpressServer.registerWebController(MerchantController);
        this.ExpressServer.registerWebController(WallController);
        this.ExpressServer.registerWebController(MarketingController);
        this.ExpressServer.registerWebController(DashboardController);
        this.ExpressServer.registerWebController(SettingsController);
        this.ExpressServer.registerWebController(DefaultController);
        this.ExpressServer.registerWebController(BlueMPController);
        this.ExpressServer.registerWebController(DianpingController);
        this.ExpressServer.registerWebController(PartnerController);
        this.ExpressServer.registerWebController(ErrorController);
        this.logger.info('Finished registering  web controllers.');
    }

    /**
     * Use the getter to implement the singleton pattern
     */
    static get instance() {
        if(!this._instance || (this._instance && !(this._instance instanceof WebApplication))) {
            this._instance = new WebApplication();
        }

        return this._instance;
    }

    /**
     * Initialize the application's event handers
     * 
     * @name initializeEventHandler
     * 
     * @memberOf WebApplication
     */
    initializeEventHandler() {
        this.addEventListener(ApplicationEvents.TERMINATING, this.terminatingEventHandler);
        this.addEventListener(ApplicationEvents.MAINTAINING, this.maintainancingEventHandler);
        this.addEventListener(ApplicationEvents.RESUME, this.resumeEventHandler);
        this.addEventListener(ApplicationEvents.UNCAUGHT_EXCEPTION, this.uncaughtExceptionEventHandler);
        this.addEventListener(ApplicationEvents.SERVICE_ERROR, this.serviceErrorEventHandler);
        this.addEventListener(ApplicationEvents.WEB_REQUEST_ERROR, this.webRequestErrorEventHandler);
    }

    get terminating() {
        return this._terminating;
    }
    /**
     * The event handler when the application receive SIGTERM signal
     * 
     * @name terminatingEventHandler
     * 
     * @memberOf WebApplication
     */
    terminatingEventHandler() {
        this._terminating = true;
        this.logger.info(`${this.name} received exit signal, will gracefully quit application...`);
        this.gracefullyQuit();
    }

    /**
     * The function to quit application gracefully
     * 
     * @param {any} error
     * 
     * @memberOf WebApplication
     */
    gracefullyQuit() {
        var app = this;
        let waitTime = this.Configuration.gracefully_terminate_wait_time || 3000;
        let timerId = 0;

        this.emit(ApplicationEvents.MAINTAINING);

        timerId = setTimeout(() => {
            clearTimeout(timerId);
            app.quit();
        }, waitTime);
    }

    maintainancingEventHandler() {
        if (this.terminating) {
            return;
        }
        this.logger.info('The application is just turned into maintainance mode.')
        this._IsUnderMaintainancing = true;
        //TODO: add log here
    }

    resumeEventHandler() {
        this.logger.info('The application is just turned into normal mode.');
        this._IsUnderMaintainancing = false;
    }

    /**
     * The uncaught exception handler
     * 
     * @name uncaughtExceptionEventHandler
     * @param {Error} error - The exception 
     * 
     * @memberOf WebApplication
     */
    uncaughtExceptionEventHandler(error) {
        this.logger.error("Uncaught exception happened: ", error);

        this.logger.info("Application caught an unhandler exception, will quit gracefully ...");
        this.gracefullyQuit();
    }

    /**
     * Handle the errors happened in service classes
     * 
     * @name serviceErrorEventHandler
     * @param {Error} error
     * 
     * @memberOf WebApplication
     */
    serviceErrorEventHandler(error) {
        this.logger.error(error);
        this.opsService.sendServiceErrorMail(error);
    }

    /**
     * Handle the errors happened in web request
     * 
     * @name webRequestErrorEventHandler
     * @param {Error} error
     * 
     * @memberOf WebApplication
     */
    webRequestErrorEventHandler(error) {
        this.logger.error(error);
        this.opsService.sendWebRequestErrorMail(error);
    }

    quit() {
        this.ExpressServer.stop();
        super.quit();
    }

    /**
     * Override the parent class' run method
     */
    run() {
        /*
        //TODO: should support cluster mode
        if (cluster.isMaster) {
            const numCPUs = require('os').cpus().length;
            for (var i = 0; i < numCPUs; i++) {
                cluster.fork();
            }
        } else {*/
            this.ExpressServer.start(() => {
                this.logger.info(`Finished initializing ${this.name}. The system start to serve.`)
            });
        /*}*/
        
    }
}

exports.WebApplication = WebApplication;